嗨,大家今天過得好嗎?歡迎來到鐵人賽 Day 9。
昨天我們成功在前端把 APIService 的假資料(Mock Data)接上了 Controller,現在只要按下按鈕,我們的 isLoading 狀態就會變成 true,兩秒後又會把資料塞進 resultMenu 裡。
資料流是通了,但如果你現在去按按鈕,畫面上什麼反應都沒有(只有 Console 在默默印字)。這在 UX(使用者體驗)上是絕對不合格的。特別是未來我們要串接真正的 LLM,AI 生成文字通常需要 3 到 5 秒甚至更久,如果沒有「轉圈圈」動畫,使用者一定會以為 App 當機了,然後瘋狂連點按鈕。
今天,我們就要來用 Vibe Coding 完成前端介面的最後一塊拼圖:讀取狀態與結果卡片。
老規矩,我們絕對不對 AI 說:「幫我把 Loading 畫面跟結果畫面加進去」。如果你這樣說,AI 保證會用一堆 if-else 把你的 ContentView 塞爆。
我們要繼續發揮昨天「元件化」的精神。把設計需求拆成兩個部分:
ResultCardView): 專門用來顯示 HardwareMenu 的精美 UI。打開你的 Prompt 視窗,下這段指令:
請幫我用 SwiftUI 寫一個獨立的 `ResultCardView.swift` 元件。
它會接收我昨天定義的 `HardwareMenu` 型別資料。
設計要求:
1. 採用卡片式設計,白色背景、圓角、帶有輕微陰影。
2. 頂部要有個標題「 你的專屬 AI 菜單」。
3. 清楚列出 CPU 與 GPU 的型號,可以用 HStack 搭配圖示 (SF Symbols)。
4. 總價 (totalPrice) 要特別放大並用醒目的顏色標示。
5. 最下方用一個淺色背景的區塊,顯示 AI 的推薦理由 (reason)。
拿到的程式碼請放進 Views 資料夾,大概會長這樣,非常乾淨:
// ResultCardView.swift
import SwiftUI
struct ResultCardView: View {
let menu: HardwareMenu
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text(" 你的專屬 AI 菜單")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.purple)
Divider()
// 硬體清單
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "cpu")
.foregroundColor(.blue)
Text("CPU: \(menu.cpu)")
.font(.headline)
}
HStack {
Image(systemName: "memorychip")
.foregroundColor(.green)
Text("GPU: \(menu.gpu)")
.font(.headline)
}
}
// 總價
HStack {
Text("總金額預估:")
.foregroundColor(.gray)
Spacer()
Text("NT$ \(menu.totalPrice)")
.font(.title)
.fontWeight(.heavy)
.foregroundColor(.red)
}
.padding(.vertical, 8)
// AI 推薦理由
VStack(alignment: .leading, spacing: 8) {
Text("💡 AI 推薦理由")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.gray)
Text(menu.reason)
.font(.subheadline)
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.padding()
.background(Color.blue.opacity(0.05))
.cornerRadius(10)
}
.padding()
.background(Color.white)
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5)
}
}
元件準備好了,接下來就是修改 ContentView,把狀態串起來。你可以自己改,或是叫 AI 幫你加上 ProgressView 和動畫。
將 ContentView 更新為以下結構:
// ContentView.swift
import SwiftUI
struct ContentView: View {
@StateObject private var controller = HomeController()
var body: some View {
NavigationView {
ZStack {
Color(.systemGray6).edgesIgnoringSafeArea(.all)
ScrollView {
VStack(spacing: 25) {
// 1. 輸入表單區塊
InputCardView(
requestData: $controller.requestData,
purposeOptions: controller.purposeOptions
)
// 2. 狀態判斷與結果渲染
if controller.isLoading {
// 讀取中的畫面
VStack {
ProgressView()
.scaleEffect(1.5)
.padding()
Text("AI 正在思考最適合你的配置...")
.foregroundColor(.gray)
}
.padding(.top, 20)
} else if let menu = controller.resultMenu {
// 成功拿到資料,顯示結果卡片
ResultCardView(menu: menu)
.transition(.scale.combined(with: .opacity)) // 加入縮放與淡入動畫
}
Spacer()
// 3. 送出按鈕 (如果正在讀取就 Disable 避免連點)
Button(action: {
controller.submitRequest()
}) {
Text(controller.isLoading ? "運算中..." : "產生推薦菜單")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(
LinearGradient(
gradient: Gradient(colors: controller.isLoading ? [Color.gray, Color.gray] : [Color.blue, Color.purple]),
startPoint: .leading, endPoint: .trailing
)
)
.cornerRadius(12)
}
.disabled(controller.isLoading) // 讀取時禁用按鈕
.padding(.bottom, 20)
}
.padding()
}
}
.navigationTitle("AI 硬體配對")
// 讓畫面的出現與消失都有平滑的過渡動畫
.animation(.easeInOut(duration: 0.3), value: controller.isLoading)
.animation(.spring(), value: controller.resultMenu != nil)
}
}
}
現在,按下 Cmd + R 把模擬器跑起來。
輸入預算,選擇用途,點下「產生推薦菜單」。你會看到按鈕變成灰色的「運算中...」,畫面上出現轉圈圈動畫與提示文字;兩秒後(Mock Data 的延遲結束),轉圈圈消失,一張精美、帶有 AI 推薦理由的卡片會用充滿彈性的動畫(Spring Animation)彈出來。
太感人了,我們的前端 MVP(最小可行性產品)視覺與互動已經完全到位!
這幾天下來,我們利用 Vibe Coding 極速完成了 iOS 的介面、分離了 MVC 架構、實作了 API Service、甚至搞定了非同步的狀態轉換與動畫。這就是我在 Day 1 提到的「雙效加速器」中,屬於前端的飆速快感。
但在我們把戰場正式切換回 n8n 寫後端之前,明天我想先暫停一下。我們來聊聊這幾天 Vibe Coding 實戰中,「如果不懂基礎語法,你會踩到哪些致命的雷?又該如何 Debug?」 這是一篇送給所有依賴 AI 開發者的心法指南。
我們 Day 10 見!